第三届“数信杯”数据安全大赛 WP by 静安
关注泷羽Sec和泷羽Sec-静安公众号,这里会定期更新与 OSCP、渗透测试等相关的最新文章,帮助你理解网络安全领域的最新动态。
01 数据安全
4、数据存储1
- 题目名称: 数据存储1
- 分值: 100分
- 描述: 工程师小王开发了对数据处理的程序,分析程序功能,解密文件获取原始数据,提交第6行第2列数据。
文件分析
1. 附件内容
$ unzip re87a57766.zip
Archive: re87a57766.zip
Length Date Time Name
--------- ---------- ----- ----
3296 2025-12-02 05:20 info_19ff9a2.ori.en
14552 2025-12-02 05:19 re87a577662. 文件类型
$ file re87a57766
re87a57766: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
dynamically linked, stripped
$ file info_19ff9a2.ori.en
info_19ff9a2.ori.en: ASCII text, with CRLF line terminators程序分析
1. 字符串分析
$ strings re87a57766 | grep -E "(info|base64|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789)"
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
./info_19ff9a2.ori
info_19ff9a2.ori.en关键发现:
- 程序使用 Base64 字符表
- 输入文件:
./info_19ff9a2.ori - 输出文件:
info_19ff9a2.ori.en
2. 加密逻辑推断
根据程序字符串和文件名:
.ori→ 原始文件(Original).ori.en→ 加密文件(Encrypted)- 使用 Base64 编码进行”加密”
解密过程
1. 查看加密文件
$ head -3 info_19ff9a2.ori.en
MjY1MjMxNDY5NDEgNzA2MTYzMTk2MDIxMTU1NzIyIGE5ZWFjNzNAMWQzLmNuCg==
MjY2MjEzODU2NTUgODg1ODgyMTk4ODMwMjU3MTgzIDE4Y2E3ZWU5QDI2MC5jb20uY24K
MzU2MTk4ODY0NzQgNzQ3ODUyMTk3NTI5MTI3Njc5IDM1N2M2NUAzNGUuY29tLmNuCg==2. Base64 解码
import base64
# 解码第1行
line1 = "MjY1MjMxNDY5NDEgNzA2MTYzMTk2MDIxMTU1NzIyIGE5ZWFjNzNAMWQzLmNuCg=="
decoded = base64.b64decode(line1).decode('utf-8')
print(decoded)
# 输出: 26523146941 706163196021155722 a9eac73@1d3.cn数据格式: 手机号 身份证号 邮箱
3. 完整解密脚本
#!/usr/bin/env python3
import base64
with open('info_19ff9a2.ori.en', 'r') as f:
lines = f.readlines()
decoded_lines = []
for line in lines:
line = line.strip()
if line:
decoded = base64.b64decode(line).decode('utf-8')
decoded_lines.append(decoded)
print(decoded)
# 获取第6行第2列
row6 = decoded_lines[5] # 索引从0开始
columns = row6.split()
answer = columns[1] # 第2列(索引1)
print(f"\n答案: {answer}")解密结果
第6行完整数据
25710876040 716896198829037493 ad7862@b7.com数据分列
- 第1列(手机号):
25710876040 - 第2列(身份证号):
716896198829037493 - 第3列(邮箱):
ad7862@b7.com
答案
第6行第2列数据: 716896198829037493
解题工具
#!/usr/bin/env python3
"""数据存储1 - 快速解密工具"""
import base64
def decrypt_and_get_answer(file_path, row=6, col=2):
with open(file_path, 'r') as f:
lines = [base64.b64decode(line.strip()).decode('utf-8')
for line in f if line.strip()]
target = lines[row-1].split()[col-1]
return target
# 使用方法
answer = decrypt_and_get_answer('info_19ff9a2.ori.en', 6, 2)
print(f"答案: {answer}")Flag: 716896198829037493
6、数据隐藏(100分):
题目描述:某汽车供应链物流中台正在进行季度数据归档,由于归档任务占用了主索引资源,运维团队启用了一套“底层应急索引机制”。该机制并不依赖 SQLite 原生的索引,而是设计了一套自定义的跨页链表协议,将关键筛选逻辑碎片化地存储在数据库文件的物理空闲块 (Freeblocks) 数据区中。 请检查 sys_config 表,获取底层链表的入口指针以及自定义链表节点的结构定义。根据结构定义,从底层物理空间中提取并重组出“特定批次货物筛选脚本”(SQL)。隐写数据位于 SQLite Freeblock 的有效载荷区(跳过 Freeblock 自身的 4 字节头部)。数据经过了异或处理,密钥与所在物理页号有关。运行提取出的脚本,定位出该批次雷达模组所在的 集装箱编号 (container_id) 和 车牌号 (license_plate),最终需要将 container_id 和 license_plate 的后五位数字使用下划线连接提交,例如:CN2877541671_72345。
题目分析
题目要求从SQLite数据库的Freeblock中提取隐藏的SQL脚本,执行后获取特定批次货物的集装箱编号和车牌号。
解题步骤
1. 查看数据库结构和配置
sqlite3 sqlite.db查看 sys_config 表获取关键配置信息:
sqlite> .tables
drivers orders sys_config warehouses
sqlite> SELECT * FROM sys_config;
recovery.pointer|ERR_PTR: 0000013B:04F0|Start address of emergency chain
recovery.structure|Struct: >IHH (NextPage, NextOff, Len)|Custom header inside freeblocks
recovery.encryption|XOR_PAGE_ID_BE|Encryption mode2. 解析配置参数
-
recovery.pointer:
0000013B:04F0- 页号:0x13B = 315 (十进制)
- 偏移:0x04F0 = 1264 (十进制)
-
recovery.structure:
>IHH (NextPage, NextOff, Len)- 大端格式,4字节下一页号 + 2字节下一偏移 + 2字节数据长度
- 总共8字节自定义头部
-
recovery.encryption:
XOR_PAGE_ID_BE- 使用大端字节序的页号作为XOR密钥
3. 编写数据提取脚本
创建 extract.py:
#!/usr/bin/env python3
import struct
# 读取数据库文件
with open('sqlite.db', 'rb') as f:
data = f.read()
# 获取页面大小
page_size = struct.unpack('>H', data[16:18])[0]
print(f"[*] 页面大小: {page_size} 字节")
# 配置参数
entry_page = 0x13B # 315
entry_offset = 0x04F0 # 1264
extracted_sql = []
current_page = entry_page
current_offset = entry_offset
visited = set()
max_iterations = 1000
iteration = 0
print("[*] 开始遍历链表...\n")
while iteration < max_iterations:
location = (current_page, current_offset)
if location in visited:
break
visited.add(location)
iteration += 1
# 计算物理地址
physical_addr = (current_page - 1) * page_size + current_offset
if physical_addr + 8 > len(data):
break
# 读取自定义头部 (8字节: >IHH)
header_bytes = data[physical_addr:physical_addr + 8]
next_page, next_offset, data_len = struct.unpack('>IHH', header_bytes)
print(f"[{iteration}] 页{current_page}+0x{current_offset:04X}: NextPage={next_page}, NextOff=0x{next_offset:04X}, Len={data_len}")
if data_len == 0 or data_len > 10000:
break
# 读取加密数据
data_start = physical_addr + 8
if data_start + data_len > len(data):
break
encrypted_data = data[data_start:data_start + data_len]
# XOR解密:使用大端字节序的页号
page_bytes = struct.pack('>I', current_page)
decrypted = bytearray()
for i, byte in enumerate(encrypted_data):
key_byte = page_bytes[i % 4]
decrypted.append(byte ^ key_byte)
chunk = decrypted.decode('utf-8', errors='ignore')
extracted_sql.append(chunk)
# 跳转到下一个节点
if next_page == 0 or next_page == 0xFFFFFFFF:
break
current_page = next_page
current_offset = next_offset
# 输出完整SQL
full_sql = ''.join(extracted_sql)
print("\n提取的SQL:\n" + "="*70)
print(full_sql)
# 保存到文件
with open('extracted_sql.txt', 'w', encoding='utf-8') as f:
f.write(full_sql)4. 执行提取脚本
python3 extract.py提取出的SQL内容:
-- [EMERGENCY_INDEX] Lidar Batch Locator
SELECT
t1.container_id,
t2.license_plate
FROM orders t1
JOIN drivers t2 ON t1.driver_id = t2.driver_id
WHERE
t1.route_path LIKE '%深圳转运心%'
AND t1.weight_kg BETWEEN 45.50 AND 45.60
AND t2.phone LIKE '%9527'
AND t1.handling_code = 'LIDAR_QC_HOLD';5. 执行SQL查询
sqlite3 sqlite.db.mode column
.headers on
SELECT
t1.container_id,
t2.license_plate
FROM orders t1
JOIN drivers t2 ON t1.driver_id = t2.driver_id
WHERE
t1.route_path LIKE '%深圳转运心%'
AND t1.weight_kg BETWEEN 45.50 AND 45.60
AND t2.phone LIKE '%9527'
AND t1.handling_code = 'LIDAR_QC_HOLD';查询结果:
container_id license_plate
------------ -------------
CN2888991777 粤B-52816
6. 提取答案
根据题目要求,提取 container_id 和 license_plate 的后五位数字:
CN2888991777→ 后5位:91777粤B-52816→ 后5位数字:52816
Flag
CN2888991777_528167、数据加密(100分):
附件下载
题目描述:某加密产品采用标准算法进行数据安全防护,某安全研究员通过逆向分析得到该产品的代码后发现,这段代码模拟了某商用密码库接口中可能存在的双重填充场景。请分析题目提供的代码文件,基于截获的密文及侧信道数据,恢复原始数据。
task.py 代码结构
def padding(msg):
tmp = 16 - len(msg) % 16
pad = format(tmp, '02x')
return bytes.fromhex(pad * tmp) + msg
message = padding(flag) # 第一次填充
hint = bytes_to_long(key) ^ bytes_to_long(message[:16]) # 侧信道泄露
message = pad(message, 16, 'pkcs7') # 第二次填充 (PKCS7)
IV = os.urandom(16)
encryption = AES.new(key, AES.MODE_CBC, iv=IV)
enc = encryption.encrypt(message)已知信息
enc = 1ce1df3812668ce0bccd86c146cc56989681e128edd0676f5d26e01abdee90c860e22a5a491f94ac5ca3ab02242740fb8c35a3b60ea737ca0d2662fba2b0e299hint = 32393f4e3c3c4f3e323a512a5356437d- flag长度 = 38字节
- flag格式:
flag{...} - key长度 = 16字节
双重填充分析
第一次填充(自定义padding):
- flag长度: 38字节
- 38 % 16 = 6
- 需要填充: 16 - 6 = 10字节
- 填充值:
0x0a(10的十六进制) - 填充位置: 前面
- 结果:
[0x0a * 10] + flag= 48字节
第二次填充(PKCS7):
- 输入: 48字节
- 48 % 16 = 0
- PKCS7规则: 即使整块,也要添加一个完整块的填充
- 填充:
[0x10 * 16]= 16字节 - 结果:
[0x0a * 10] + flag + [0x10 * 16]= 64字节 (4个AES块)
侧信道信息利用
Hint泄露:
hint = key XOR message[:16]其中 message[:16] 在第一次填充后是:
[0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 'f', 'l', 'a', 'g', '{', ?]我们知道前15字节,只有第16字节未知(flag的第6个字符)。
攻击步骤
步骤1: 爆破第16字节
对于每个可能的ASCII字符 c (32-126):
- 构造完整的16字节:
known_15_bytes + c - 计算密钥:
key = hint XOR constructed_16_bytes
步骤2: 验证密钥正确性
使用候选密钥解密,利用CBC特性:
- CBC解密:
P[i] = D(C[i]) XOR C[i-1] - 第一块:
P[0] = D(C[0]) XOR IV(IV未知) - 后续块: 可以正确解密(不依赖IV)
验证方法:
- 解密第4块(最后一块)
- 检查是否为PKCS7填充:
[0x10] * 16 - 如果匹配,说明密钥可能正确
步骤3: 恢复IV
利用已知的第一块明文:
IV = D(C[0]) XOR P[0]步骤4: 完整解密
使用恢复的key和IV进行完整解密:
- AES-CBC解密
- 去除PKCS7填充
- 去除第一次自定义填充
解密实现
from Crypto.Cipher import AES
from Crypto.Util.number import *
from Crypto.Util.Padding import unpad
enc = bytes.fromhex('1ce1df3812668ce0bccd86c146cc56989681e128edd0676f5d26e01abdee90c860e22a5a491f94ac5ca3ab02242740fb8c35a3b60ea737ca0d2662fba2b0e299')
hint = int('32393f4e3c3c4f3e323a512a5356437d', 16)
# 已知前15字节
padding_1st = 10
known_prefix = bytes([padding_1st] * padding_1st) + b'flag{'
# 分割密文块
blocks = [enc[i:i+16] for i in range(0, len(enc), 16)]
# 爆破第16字节
for byte_val in range(32, 127):
test_msg_block1 = known_prefix + bytes([byte_val])
test_key = long_to_bytes(hint ^ bytes_to_long(test_msg_block1), 16)
# 使用ECB解密各块
cipher = AES.new(test_key, AES.MODE_ECB)
dec_blocks = [cipher.decrypt(b) for b in blocks]
# 检查最后一块是否为PKCS7填充
last_pt = bytes([dec_blocks[3][j] ^ blocks[2][j] for j in range(16)])
if last_pt == bytes([0x10] * 16):
# 密钥正确!恢复IV
iv = bytes([dec_blocks[0][j] ^ test_msg_block1[j] for j in range(16)])
# 完整解密
cipher_cbc = AES.new(test_key, AES.MODE_CBC, iv=iv)
plaintext = unpad(cipher_cbc.decrypt(enc), 16)
# 去除第一次填充
flag = plaintext[padding_1st:]
if flag.startswith(b'flag{') and flag.endswith(b'}'):
print(f"Flag: {flag.decode()}")
break解密结果
Flag: flag{IADMIN-TOP-18880101-7634567_2025}
密钥: 38333544363645343830374632313834
IV: 8e3f1bd4fc5e355ee7f42faf0718491e验证
密文结构:
Block 0: 1ce1df3812668ce0bccd86c146cc5698 <- [0a]*10 + 'flag{I'
Block 1: 9681e128edd0676f5d26e01abdee90c8 <- 'ADMIN-TOP-18880'
Block 2: 60e22a5a491f94ac5ca3ab02242740fb <- '101-7634567_202'
Block 3: 8c35a3b60ea737ca0d2662fba2b0e299 <- '5}' + [0x10]*16爆破命中:
- 第16字节 = ‘I’ (0x49)
- flag第6个字符 = ‘I’

9、数据泄露(100分): 附件下载
题目描述:分析题目附件,获取陈淑华编号信息进行提交。
5aeT5ZCNOumZiOa3keWNjiznvJblj7c6UzIwMjUxMDAxLOi6q+S7veivgeWPt+eggTo0NDE4ODEyMDAwMDUwMzY0NTA=
flag:S20251001
10、数据隐写(100分):
题目描述:某黑客团伙将核心机密(flag)隐藏在一张普通图片中,并通过多模态 AI 模型建立了 “图片特征→流量特征” 的映射关系。该模型可将图片中的隐写特征转换为流量特征,而这些流量特征直接编码了 flag。现提供图片、AI 模型及提示信息,请你破解隐写信息,调用模型转换特征,最终还原出 flag。
附件文件:
secret_image.png- 隐写图片 (775x482, RGB)multimodal_model.pth- PyTorch神经网络模型stego_hint.txt- 提示文件
提示信息分析
隐写规则提示:
1. 图片的红色(R)通道中隐藏了模型输入特征;
2. 取图片左上角前20个像素的R值,计算 R值 mod 10 得到20维特征;
3. 20维特征输入multimodal_model.pth模型后,输出的数值取整即为flag的ASCII码;
4. ASCII码转换为字符即可得到完整flag。
模型提示:
- 模型为轻量全连接神经网络(MLP),仅含3层线性层+ReLU激活。解题思路
步骤1: 图片特征提取
根据提示,需要从图片提取20维特征:
- 读取图片的R通道(红色通道)
- 提取”左上角前20个像素”
- 对每个R值取模10
关键问题: “前20个像素”的顺序是什么?
可能的遍历方式:
- 按行优先:从左到右,从上到下
- 按列优先:从上到下,从左到右
- 矩形区域:4x5, 5x4, 2x10等
步骤2: 模型结构分析
加载PyTorch模型检查点:
checkpoint = torch.load('multimodal_model.pth', map_location='cpu')从权重张量推断模型结构:
input_dim = checkpoint['fc1.weight'].shape[1] # 20
hidden1_dim = checkpoint['fc1.weight'].shape[0] # 64
hidden2_dim = checkpoint['fc2.weight'].shape[0] # 32
output_dim = checkpoint['fc3.weight'].shape[0] # 27模型结构:
输入层: 20维 → 64维 (Linear + ReLU)
隐藏层: 64维 → 32维 (Linear + ReLU)
输出层: 32维 → 27维 (Linear)27个输出对应27个字符的ASCII码。
步骤3: 遍历测试
由于”前20个像素”的顺序不明确,需要尝试不同的提取方式:
完整解决方案
代码实现
#!/usr/bin/env python3
import torch
import torch.nn as nn
from PIL import Image
import numpy as np
# 定义模型结构
class MultiModalModel(nn.Module):
def __init__(self, input_dim=20, hidden1_dim=64, hidden2_dim=32, output_dim=27):
super(MultiModalModel, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden1_dim)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(hidden1_dim, hidden2_dim)
self.relu2 = nn.ReLU()
self.fc3 = nn.Linear(hidden2_dim, output_dim)
def forward(self, x):
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
x = self.relu2(x)
x = self.fc3(x)
return x
# 加载图片
img = Image.open('secret_image.png')
img_array = np.array(img)
# 加载模型
checkpoint = torch.load('multimodal_model.pth', map_location='cpu')
model = MultiModalModel()
model.load_state_dict(checkpoint)
model.eval()
# 提取特征 - 按列优先(上到下)
features = []
for row in range(20):
r_value = img_array[row, 0, 0] # 第1列,前20行
features.append(r_value % 10)
features = np.array(features, dtype=np.float32)
print(f"提取的特征: {features}")
# 模型推理
with torch.no_grad():
input_tensor = torch.tensor(features).unsqueeze(0)
output = model(input_tensor)
ascii_codes = output.squeeze().numpy().round().astype(int)
flag = ''.join([chr(code) for code in ascii_codes])
print(f"Flag: {flag}")测试结果
测试不同的提取方式:
| 方法 | 提取方式 | 结果 | 是否正确 |
|---|---|---|---|
| 1 | 按行优先(第1行前20列) | ag\au>//EZmco.rdi/]cZ0-/2w` | ❌ |
| 2 | 按列优先(前20行第1列) | flag{A12I_shu1xin2bei_2025} | ✅ |
| 3 | 4x5矩形区域 | ci^dw?00G\per/tfk0bf\1/13y` | ❌ |
正确的提取方式: 按列优先,即前20行第1列的R值

特征向量
像素位置: (0,0), (1,0), (2,0), ..., (19,0)
R值: 253, 258, 257, 252, 259, 251, 254, 250, 255, 256, ...
特征: 3, 8, 7, 2, 9, 1, 4, 0, 5, 6, ...完整特征向量:
[3, 8, 7, 2, 9, 1, 4, 0, 5, 6, 8, 7, 9, 2, 1, 4, 0, 5, 3, 6]模型输出
ASCII码: [102, 108, 97, 103, 123, 65, 49, 50, 73, 95,
115, 104, 117, 49, 120, 105, 110, 50, 98, 101,
105, 95, 50, 48, 50, 53, 125]
字符: f l a g { A 1 2 I _
s h u 1 x i n 2 b e
i _ 2 0 2 5 }答案
flag{A12I_shu1xin2bei_2025}安全的多模态隐写
# 正确的实现方式
class SecureStego:
def __init__(self, key):
self.key = key
self.model = load_server_model() # 服务端模型
def embed(self, image, message):
# 使用密钥加密消息
encrypted = encrypt(message, self.key)
# 动态生成隐写位置
positions = derive_positions(self.key, image.shape)
# 嵌入加密数据
stego_image = embed_at_positions(image, encrypted, positions)
return stego_image
def extract(self, stego_image):
# 需要密钥才能提取
positions = derive_positions(self.key, stego_image.shape)
encrypted = extract_from_positions(stego_image, positions)
# 调用服务端API解密
message = api_decrypt(encrypted, self.key)
return message02 数据分析
数据处理(第1题):
-
分值:25分
-
题干内容:
请访问 http://139.224.55.37 下载考题附件,附件名称:
数据处理.zip
第一天上班的你去查看公司的流量监控记录,发现了一串非常奇怪的流量信息,你判断出这是黑客攻击所产生的流量,请你分析流量,找出泄露的信息。 -
答案要求:
提交泄露的管理员账号和密码,格式如:
123/123
解题思路
这是一道典型的流量分析题,需要从pcap流量包中找出攻击者的行为并提取关键信息。
解题步骤
Step 1: 解压附件
unzip 数据处理.zip得到三个文件:
attack.pcapng- 流量包文件(主要分析对象)#U5c45#U6c11#U4fe1#U606f#U8868.csv- 居民信息表#U9898#U76ee#U4fe1#U606f.pdf- 题目信息
Step 2: 流量包基础分析
使用Wireshark或命令行工具查看流量包基本信息:
# 使用tshark查看协议统计
tshark -r attack.pcapng -q -z io,phs发现:
- 总数据包数:3353个
- 协议类型:全部为TCP
- 主要端口:5000(服务器端口)
Step 3: 识别攻击类型
通过分析TCP流量,发现大量重复的HTTP POST请求到 /login 接口:
import dpkt
# 读取pcap文件
with open('attack.pcapng', 'rb') as f:
pcap = dpkt.pcapng.Reader(f)
packets = list(pcap)
# 分析HTTP POST请求
for ts, buf in packets:
eth = dpkt.ethernet.Ethernet(buf)
if isinstance(eth.data, dpkt.ip.IP):
ip = eth.data
if isinstance(ip.data, dpkt.tcp.TCP):
tcp = ip.data
if b'POST /login' in tcp.data:
print("发现POST登录请求")判断:这是一次暴力破解攻击(Brute Force Attack)
Step 4: 提取暴力破解凭证
编写脚本提取所有尝试的用户名和密码:
import dpkt
import re
from urllib.parse import unquote
credentials = []
for ts, buf in packets:
try:
eth = dpkt.ethernet.Ethernet(buf)
ip = eth.data
tcp = ip.data
if len(tcp.data) > 0:
data_str = tcp.data.decode('utf-8', errors='ignore')
# 查找POST登录请求
if 'POST /login' in data_str and 'username=' in data_str:
if '\r\n\r\n' in data_str:
body = data_str.split('\r\n\r\n', 1)[1]
decoded_body = unquote(body)
# 提取用户名和密码
username = re.search(r'username=([^&]+)', decoded_body)
password = re.search(r'password=([^&\s]+)', decoded_body)
if username and password:
credentials.append(
(username.group(1), password.group(1))
)
except:
pass
print(f"共提取 {len(credentials)} 组凭证")结果:共提取到 261 组凭证
暴力破解密码列表(部分):
admin:1
admin:123456.com
admin:123123
admin:idc123!@#
admin:123
admin:aaa123!@#
...
admin:Adm1n@2024#Secure!Pass ← 关键密码Step 5: 识别成功的登录
分析HTTP响应,查找登录成功的标志:
# 统计HTTP响应的Content-Length
response_lengths = {}
for ts, buf in packets:
try:
eth = dpkt.ethernet.Ethernet(buf)
ip = eth.data
tcp = ip.data
# 只看服务器响应(sport=5000)
if tcp.sport == 5000 and len(tcp.data) > 0:
data_str = tcp.data.decode('utf-8', errors='ignore')
if 'Content-Length:' in data_str:
match = re.search(r'Content-Length: (\d+)', data_str)
if match:
length = int(match.group(1))
response_lengths[length] = response_lengths.get(length, 0) + 1
except:
pass
for length, count in sorted(response_lengths.items()):
print(f"长度 {length}: {count} 次")关键发现:
| Content-Length | 出现次数 | 含义 |
|---|---|---|
| 5500 | 259 | 登录失败页面 |
| 189 | 2 | 302重定向(登录成功!) |
| 1766301 | 1 | 登录后的数据页面 |
Step 6: 定位成功的登录凭证
查找返回302重定向的请求对应的凭证:
# 查找302响应
for stream_key, packets_list in tcp_streams.items():
for pkt in packets_list:
data_str = pkt['data'].decode('utf-8', errors='ignore')
if 'HTTP/1.1 302 FOUND' in data_str:
print("找到302重定向!")
print(data_str)
# 找到对应的POST请求
for req_pkt in packets_list:
req_str = req_pkt['data'].decode('utf-8', errors='ignore')
if 'POST /login' in req_str:
# 提取凭证...成功的HTTP响应:
HTTP/1.1 302 FOUND
Server: Werkzeug/3.1.4 Python/3.12.12
Date: Tue, 09 Dec 2025 03:06:18 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 189
Location: /
Vary: Cookie
Set-Cookie: session=eyJsb2dnZWRfaW4iOnRydWV9.aTeSKg.wNp00wl0i77Wq6sQpxH_rHrjtT8; HttpOnly; Path=/
Connection: close对应的登录请求:
POST /login HTTP/1.1
Host: 172.16.4.135:32768
Content-Type: application/x-www-form-urlencoded
Content-Length: 48
username=admin&password=Adm1n%402024%23Secure%21PassURL解码后:
username=admin&password=Adm1n@2024#Secure!PassStep 7: 验证Flag
Flag格式:用户名:密码
Flag: admin/Adm1n@2024#Secure!Pass
解题脚本
完整的自动化解题脚本:
#!/usr/bin/env python3
import dpkt
import socket
import re
from urllib.parse import unquote
def solve():
pcap_file = 'attack.pcapng'
with open(pcap_file, 'rb') as f:
try:
pcap = dpkt.pcapng.Reader(f)
packets = list(pcap)
except:
f.seek(0)
pcap = dpkt.pcap.Reader(f)
packets = list(pcap)
# 按TCP流组织数据
tcp_streams = {}
for ts, buf in packets:
try:
eth = dpkt.ethernet.Ethernet(buf)
if not isinstance(eth.data, dpkt.ip.IP):
continue
ip = eth.data
if not isinstance(ip.data, dpkt.tcp.TCP):
continue
tcp = ip.data
src_ip = socket.inet_ntop(socket.AF_INET, ip.src)
dst_ip = socket.inet_ntop(socket.AF_INET, ip.dst)
if src_ip < dst_ip:
stream_key = (src_ip, tcp.sport, dst_ip, tcp.dport)
else:
stream_key = (dst_ip, tcp.dport, src_ip, tcp.sport)
if stream_key not in tcp_streams:
tcp_streams[stream_key] = []
tcp_streams[stream_key].append({
'src': src_ip,
'sport': tcp.sport,
'data': tcp.data
})
except:
pass
# 查找302响应和对应的登录凭证
for stream_key, packets_list in tcp_streams.items():
for pkt in packets_list:
if len(pkt['data']) > 0:
data_str = pkt['data'].decode('utf-8', errors='ignore')
if 'HTTP/1.1 302 FOUND' in data_str:
# 找到对应的POST请求
for req_pkt in packets_list:
if len(req_pkt['data']) > 0:
req_str = req_pkt['data'].decode('utf-8', errors='ignore')
if 'POST /login' in req_str and 'username=' in req_str:
if '\r\n\r\n' in req_str:
body = req_str.split('\r\n\r\n', 1)[1]
decoded_body = unquote(body)
username_match = re.search(r'username=([^&]+)', decoded_body)
password_match = re.search(r'password=([^&\s]+)', decoded_body)
if username_match and password_match:
username = username_match.group(1)
password = password_match.group(1)
flag = f"{username}:{password}"
print(f"[+] 找到成功的登录凭证!")
print(f"[+] Flag: {flag}")
return flag
if __name__ == '__main__':
solve()运行脚本:
python3 solve.py输出:
[+] 找到成功的登录凭证!
[+] Flag: admin:Adm1n@2024#Secure!Pass数据处理(第2题)
-
分值:25分
-
题干内容:
与你交接的同事由于工作上的疏忽将原先的居民信息文件误删除了,但是你发现公司的系统上依旧存在居民的信息,下载后发现进行了脱敏处理,你需要利用技术手段将居民信息快速整理出来。
-
答案要求:
提交手机号为
18896239239的家庭住址
格式示例:若地址为“青海省沈阳市合川徐街9号”,则直接提交:青海省沈阳市合川徐街9号
解题思路
这道题考察对常见编码方式的识别和解码能力。题目提示数据经过了”脱敏处理”,需要通过技术手段恢复。
解题步骤
Step 1: 查看CSV文件内容
首先查看CSV文件的数据格式:
head -5 '#U5c45#U6c11#U4fe1#U606f#U8868.csv'输出示例:
序号,姓名,身份证号,手机号码,家庭住址,职位,单位
1,5ruh6bmP,NTAwMjM3MTk0MDA3MjkyNzk5,MTUyNzc4MzUzMjc=,6Z2S5rW355yB5rKI6Ziz5biC5ZCI5bed5b6Q6KGXOeWPtw==,5rG96L2m6KOF6aWw576O5a65,6bi/552/5oCd5Y2a5L+h5oGv5pyJ6ZmQ5YWs5Y+4
2,5p2O5biG,MjMwNDA1MjAwMzA3MjYyNTI3,MTUxNzQ3MjU2Mzc=,5rmW5YyX55yB5L2b5bGx5Y6/55m95LqR5byg6LevNTnlj7c=,5pWw5o2u6YCa5L+h5bel56iL5biI,5LiD5Zac5Lyg5aqS5pyJ6ZmQ5YWs5Y+4
...观察:
- 除了”序号”外,其他字段都是一串看似随机的字符
- 字符串以等号(=)结尾 → 疑似Base64编码
- 字符集为
A-Z,a-z,0-9,+,/,=
Step 2: 识别编码方式
Base64编码的特征:
- 只包含64个字符:
A-Z,a-z,0-9,+,/ - 使用
=作为填充字符 - 常用于数据传输和存储中的编码
Step 3: 测试解码
使用Python的base64库进行解码测试:
import base64
# 测试第一行数据
name = '5ruh6bmP'
phone = 'MTUyNzc4MzUzMjc='
address = '6Z2S5rW355yB5rKI6Ziz5biC5ZCI5bed5b6Q6KGXOeWPtw=='
print('姓名:', base64.b64decode(name).decode('utf-8'))
print('手机:', base64.b64decode(phone).decode('utf-8'))
print('住址:', base64.b64decode(address).decode('utf-8'))输出:
姓名: 满鹏
手机: 15277835327
住址: 青海省沈阳市合川徐街9号确认:数据使用Base64编码,解码后是UTF-8中文字符
Step 4: 编写解码脚本
编写Python脚本,解码所有数据并查找目标手机号:
#!/usr/bin/env python3
import base64
import csv
target_phone = '18896239239'
found = False
with open('#U5c45#U6c11#U4fe1#U606f#U8868.csv', 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
# 解码手机号
phone_encoded = row['手机号码']
phone = base64.b64decode(phone_encoded).decode('utf-8')
# 检查是否是目标手机号
if phone == target_phone:
# 解码所有信息
name = base64.b64decode(row['姓名']).decode('utf-8')
id_card = base64.b64decode(row['身份证号']).decode('utf-8')
address = base64.b64decode(row['家庭住址']).decode('utf-8')
position = base64.b64decode(row['职位']).decode('utf-8')
company = base64.b64decode(row['单位']).decode('utf-8')
print('找到目标记录!')
print('=' * 60)
print(f'序号: {row["序号"]}')
print(f'姓名: {name}')
print(f'身份证号: {id_card}')
print(f'手机号码: {phone}')
print(f'家庭住址: {address}')
print(f'职位: {position}')
print(f'单位: {company}')
print('=' * 60)
print()
print(f'答案: {address}')
found = True
break
except Exception as e:
continue
if not found:
print('未找到该手机号')Step 5: 运行脚本获取答案
python3 find_address.py输出结果:
找到目标记录!
============================================================
序号: 1395
姓名: 董帅
身份证号: 220722194309024090
手机号码: 18896239239
家庭住址: 江苏省兰州县静安阜新街19号
职位: 汽车喷漆
单位: 诺依曼软件网络有限公司
============================================================
答案: 江苏省兰州县静安阜新街19号一键解题脚本
如果想要快速解题,可以使用一行Python命令:
python3 -c "
import base64, csv
target = '18896239239'
with open('#U5c45#U6c11#U4fe1#U606f#U8868.csv', 'r', encoding='utf-8-sig') as f:
for row in csv.DictReader(f):
try:
phone = base64.b64decode(row['手机号码']).decode('utf-8')
if phone == target:
address = base64.b64decode(row['家庭住址']).decode('utf-8')
print(f'答案: {address}')
break
except: pass
"数据处理(第3题)
- 分值:25分
- 题干内容:
在得到居民信息之后,领导让你统计一下居民信息中重名的数量,方便后续的工作开展。
答案标准:
你需要统计出现重名次数出现最多的人的姓名以及出现的次数
例:重名最多的人叫张三,出现了10次,则最终提交的答案为:张三10
解题思路
这道题是第二题的延续,需要:
- 解码CSV中的所有姓名
- 统计每个姓名出现的次数
- 找出出现次数最多的姓名
解题步骤
Step 1: 解码所有姓名
延续第二题的思路,使用Base64解码姓名字段:
import base64
import csv
names = []
with open('#U5c45#U6c11#U4fe1#U606f#U8868.csv', 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
name_encoded = row['姓名']
name = base64.b64decode(name_encoded).decode('utf-8')
names.append(name)
except:
continue
print(f'总共解码了 {len(names)} 个姓名')输出:
总共解码了 2000 个姓名Step 2: 统计姓名频率
使用Python的 collections.Counter 进行频率统计:
from collections import Counter
# 统计姓名出现次数
name_counter = Counter(names)
# 获取出现次数最多的10个姓名
most_common = name_counter.most_common(10)
print('出现次数最多的前10个姓名:')
for name, count in most_common:
print(f'{name}: {count} 次')输出:
出现次数最多的前10个姓名:
刘红梅: 7 次
张丹: 6 次
李娜: 5 次
王丹丹: 5 次
王海燕: 5 次
杨婷: 4 次
刘红: 4 次
李淑华: 4 次
杨成: 4 次
张秀华: 4 次Step 3: 提取答案
# 获取重名最多的姓名和次数
top_name, top_count = most_common[0]
print(f'重名最多的人: {top_name}')
print(f'出现次数: {top_count}')
print(f'答案: {top_name}{top_count}')输出:
重名最多的人: 刘红梅
出现次数: 7
答案: 刘红梅7Step 4: 验证答案
为了确保答案正确,我们可以验证所有叫”刘红梅”的记录:
print('验证:所有叫"刘红梅"的记录')
print('=' * 80)
with open('#U5c45#U6c91#U4fe1#U606f#U8868.csv', 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
count = 0
for row in reader:
try:
name = base64.b64decode(row['姓名']).decode('utf-8')
if name == '刘红梅':
count += 1
id_card = base64.b64decode(row['身份证号']).decode('utf-8')
phone = base64.b64decode(row['手机号码']).decode('utf-8')
address = base64.b64decode(row['家庭住址']).decode('utf-8')
print(f'{count}. 序号:{row["序号"]:>4} | 身份证:{id_card} | 手机:{phone}')
except:
continue
print(f'确认:刘红梅 出现了 {count} 次')输出:
验证:所有叫"刘红梅"的记录
================================================================================
1. 序号: 320 | 身份证:360429199301219709 | 手机:13242965575
2. 序号: 461 | 身份证:654224200208144330 | 手机:15145128603
3. 序号: 664 | 身份证:513422194903038431 | 手机:15835846265
4. 序号: 785 | 身份证:360402196106309326 | 手机:14573029873
5. 序号:1822 | 身份证:45042219530907884X | 手机:15654482963
6. 序号:1880 | 身份证:440701199811034851 | 手机:13170093945
7. 序号:1964 | 身份证:411282194701306231 | 手机:13714155998
================================================================================
确认:刘红梅 出现了 7 次验证通过:确实有7个不同的人都叫”刘红梅”(身份证号和手机号都不同)
完整解题脚本
#!/usr/bin/env python3
import base64
import csv
from collections import Counter
def solve():
# 读取CSV并解码所有姓名
names = []
with open('#U5c45#U6c11#U4fe1#U606f#U8868.csv', 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
try:
name_encoded = row['姓名']
name = base64.b64decode(name_encoded).decode('utf-8')
names.append(name)
except:
continue
print(f'总共有 {len(names)} 条记录')
print()
# 统计姓名出现次数
name_counter = Counter(names)
# 找出出现次数最多的姓名
most_common = name_counter.most_common(10)
print('出现次数最多的前10个姓名:')
print('=' * 60)
for name, count in most_common:
print(f'{name}: {count} 次')
print()
print('=' * 60)
top_name, top_count = most_common[0]
print(f'重名最多的人: {top_name}')
print(f'出现次数: {top_count}')
print()
print(f'答案: {top_name}{top_count}')
return f'{top_name}{top_count}'
if __name__ == '__main__':
answer = solve()运行脚本:
python3 solve.py一行命令解题
如果想要快速获取答案:
python3 -c "
import base64, csv
from collections import Counter
names = []
with open('#U5c45#U6c11#U4fe1#U606f#U8868.csv', 'r', encoding='utf-8-sig') as f:
for row in csv.DictReader(f):
try: names.append(base64.b64decode(row['姓名']).decode('utf-8'))
except: pass
top = Counter(names).most_common(1)[0]
print(f'答案: {top[0]}{top[1]}')
"Flag
刘红梅7答案验证
7个叫”刘红梅”的人分别是:
| 序号 | 身份证号 | 手机号 | 住址 |
|---|---|---|---|
| 320 | 360429199301219709 | 13242965575 | 福建省合肥县高明王街86号12号楼2678室 |
| 461 | 654224200208144330 | 15145128603 | 安徽省秀梅县海陵刘街58号 |
| 664 | 513422194903038431 | 15835846265 | 河北省东县南溪北镇街61号7号楼1536室 |
| 785 | 360402196106309326 | 14573029873 | 青海省兴安盟市崇文杨街59号 |
| 1822 | 45042219530907884X | 15654482963 | 北京市红霞市沈河荆门街32号 |
| 1880 | 440701199811034851 | 13170093945 | 浙江省广州县怀柔潮州路19号15号楼1820室 |
| 1964 | 411282194701306231 | 13714155998 | 北京市贵阳县高明王街86号 |
可以看到,这7个人的身份证号、手机号、住址都不同,是真正的7个不同的人。
数据应急(第一题)
题目名称:磁盘取证 - 合同文件恢复
题目类型:数字取证 (Digital Forensics)
难度:中等
题目描述:
黑客在攻击时,为了对公司造成更大的破坏,直接删除了磁盘中的文件。但好在系统有自动的磁盘备份计划,保留了一个备份磁盘。请你通过技术手段,恢复出黑客删除的文件。
答案要求:
请找出删除的文件中的一个合同文件,提交合同编号。
例:如果合同编号为HT-2023-003085,则最终提交答案为:HT-2023-003085
附件:disk.img (1GB 磁盘镜像文件)
解题思路
这是一道典型的数字取证题,需要:
- 识别磁盘镜像的文件系统类型
- 使用数据恢复工具恢复已删除的文件
- 在恢复的文件中找到合同文件
- 提取合同编号
解题步骤
Step 1: 磁盘镜像基础分析
首先查看磁盘镜像的基本信息:
file disk.img输出:
disk.img: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat",
sectors/cluster 32, reserved sectors 32, root entries 512, Media descriptor 0xf8,
sectors/FAT 256, sectors/track 63, heads 64, sectors 2097144 (volumes > 32 MB),
serial number 0xdd894a27, unlabeled, FAT (16 bit)关键信息:
- 文件系统类型:FAT16
- 卷标:unlabeled(无卷标)
- 总扇区数:2097144
- 序列号:0xdd894a27
Step 2: 查看磁盘镜像十六进制头部
xxd disk.img | head -20输出分析:
00000000: eb3c 906d 6b66 732e 6661 7400 0220 2000 .<.mkfs.fat.. .
00000010: 0200 0200 00f8 0001 3f00 4000 0000 0000 ........?.@.....
00000020: f8ff 1f00 8000 2927 4a89 dd4e 4f20 4e41 ......)'J..NO NA
00000030: 4d45 2020 2020 4641 5431 3620 2020 0e1f ME FAT16 ..关键发现:
- 字节 0x00-0x02:
EB 3C 90- FAT引导跳转指令 - 字节 0x03-0x0A:
mkfs.fat- OEM标识 - 字节 0x36-0x3D:
FAT16- 文件系统类型确认
Step 3: 检查文件系统UUID
sudo blkid disk.img输出:
disk.img: SEC_TYPE="msdos" UUID="DD89-4A27" BLOCK_SIZE="512" TYPE="vfat"确认文件系统为 VFAT (FAT16),块大小为512字节。
Step 4: 尝试查看可读字符串
strings disk.img | head -100输出中的关键线索:
mkfs.fat
NO NAME FAT16
IN-SE~1ZIP
_____~1PDF
WIN-SERVER-PC-20251202-122722.raw发现:
- 存在PDF文件的8.3短文件名格式:
_____~1.PDF - 存在一个RAW文件引用
- 文件名被删除后显示为下划线
Step 5: 使用Foremost恢复删除的文件
Foremost是一个基于文件头和文件尾特征的数据恢复工具(文件雕刻技术)。
# 创建输出目录
mkdir recovered_files
# 运行foremost恢复
foremost -i disk.img -o recovered_files -vForemost工作原理:
- 扫描磁盘镜像中的二进制数据
- 识别已知文件类型的文件头(如PDF的
%PDF-) - 识别文件尾(如PDF的
%%EOF) - 提取完整的文件内容
输出:
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File
Foremost started at Sun Dec 28 01:32:21 2025
Invocation: foremost -i disk.img -o recovered_files -v
Output directory: /home/kali/Desktop/DemoDir/recovered_files
Configuration file: /etc/foremost.conf
Processing: disk.img
|------------------------------------------------------------------
File: disk.img
Start: Sun Dec 28 01:32:21 2025
Length: 1024 MB (1073741824 bytes)
Num Name (bs=512) Size File Offset Comment
*0: 00351776.pdf 8 KB 180109312
**********|
Finish: Sun Dec 28 01:32:55 2025
1 FILES EXTRACTED
pdf:= 1
------------------------------------------------------------------
Foremost finished at Sun Dec 28 01:32:55 2025关键信息:
- 成功恢复 1个PDF文件
- 文件大小:8 KB
- 文件偏移:180109312 字节(约172 MB处)
- 文件名:
00351776.pdf(Foremost自动命名,基于扇区号)
计算文件位置:
- 扇区号:351776
- 字节偏移:351776 × 512 = 180109312 字节
- 位置:~172 MB
Step 6: 查看恢复的文件
ls -R recovered_files/输出:
recovered_files/:
audit.txt pdf
recovered_files/pdf:
00351776.pdf文件结构:
audit.txt- Foremost的审计日志pdf/- 恢复的PDF文件目录00351776.pdf- 恢复的合同文件

Step 7: 提取合同编号
打开恢复的PDF文件:
# 方法1: 直接打开PDF查看
xdg-open recovered_files/pdf/00351776.pdf
# 方法2: 转换为文本后搜索
pdftotext recovered_files/pdf/00351776.pdf - | grep "HT-"
# 方法3: 使用strings搜索
strings recovered_files/pdf/00351776.pdf | grep "HT-"在PDF中发现合同编号:

HT-2025-001234Step 8: 验证答案格式
合同编号格式:HT-YYYY-NNNNNN
HT: 合同类型标识2025: 年份001234: 六位合同序号
Flag: HT-2025-001234
数据溯源- 【题目1】证书合成
请根据题目提供的证书关键参数,合成私钥解密证书。请选手找到id为285的参数合成的证书(参考附件:params.csv),可以解密哪个流量包(参考附件:pcap.zip)。并将其流量包名称作为答案提交。【答案标准】若id为285的参数合成证书,可以解密""UT5NHVWo2Z.pcap"",则答案提交为UT5NHVWo2Z.pcap的32位小写MD5值如7cb41b100d1cfbcbd1de1d795dac3fcb
题目分析
题目要求:
- 根据params.csv中id=285的参数合成RSA私钥
- 使用该私钥解密pcap.zip中的流量包
- 找出能被成功解密的流量包文件名
- 提交该文件名的MD5值作为答案
解题步骤
1. 提取ID=285的RSA参数
从params.csv中找到:
id: 285
e: 65537
p: 177264302295959185550899884811457697789837321132319354039496340545988969470422347313577084568610012957139649359576035974322283705879187577664768699213211347033624840318251940972496063336844685896882713624561971974788692556498019960846465311267474369690812099681875735569564330504277517754796899917257323134723
q: 1439901639099361296488048073215514787335670167336423355221566259733215065094584274909295088661890023228268742109619106418656023746753332062885777348760058280163791709510789344694724238407241643103430285549426656567638061780506208570708207465590949895189305890899700825224300029162867999170787851723336470285712. 计算RSA密钥参数
使用RSA算法计算:
- n = p × q
- φ(n) = (p-1) × (q-1)
- d = e^(-1) mod φ(n)
计算结果:
n = 25524315942975630524902164348980646615415631379067906424905092637569433934236415697372635457461090451139209839638834636903322814809175304746826901103004216102634794552592149840624150935097695947885611303063109481472573238304773329443506200495854657482651549528147796059266681963958098875144795832902194879880482303139612778003682586782728535759095884982208105029514574602777436707784410218940597817232989310469845223163728817066720212125675003809485140708725052901029868743431319305493958381372040484786766296673968301385185624931088751230885265032642815808366813676365040639645753384044321228415112355245904063170833
d = 7228455741056619963412792320629275084030610470352630472042335159578386160785935812491205183186258735876584747908765595937038183665079171400904943535281722551610567876102206403130815285341306998989226632052906174773501370263158109075353999743702983702000591410080154643331089571555950304754374027780715274891676290696529162804190644851022368637144593752521326584822714276957303856787647687308937746825372108560418521818966729565922760751902530859726110467937823078172426101727672997875473891637539001446454257769157050204730622735367570216285238716642347591381284427571138378138116910746249115208118659659446892689933. 生成RSA私钥
使用Python的pycryptodome库构造RSA密钥对象并导出PEM格式私钥。
4. 测试解密流量包
使用tshark工具配合生成的私钥对pcap.zip中的500个流量包进行TLS解密测试。
解密命令:
tshark -r <pcap_file> \
-o "tls.keys_list:0.0.0.0,443,http,id285_key.pem" \
-Y "http" \
-T fields \
-e http.request.uri5. 结果
经过测试,发现只有 mAqY0WRHsV.pcap 能被成功解密。
解密后可以看到HTTP流量:
POST /api/v1/user/profile这证明该证书与此流量包中的TLS会话匹配。
答案
文件名: mAqY0WRHsV.pcap
MD5值: 1f5c34eab5696a300afff7452b7f7e6a
验证
$ echo -n "mAqY0WRHsV.pcap" | md5sum
1f5c34eab5696a300afff7452b7f7e6a🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
直接回复文章中的关键词,获取更多技术资料与书单推荐!📚